/*
MIT License

Copyright (c) 2021 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo
*/

/*! \file Утилита тестирования средств лексического анализа библиотеки SIMODO core. Проект SIMODO.
*/

#include "simodo/inout/token/FileStream.h"
#include "simodo/inout/token/RefBufferStream.h"
#include "simodo/inout/token/Tokenizer.h"
#include "simodo/inout/convert/functions.h"

#include <fstream>
#include <iostream>
#include <locale>
#include <codecvt>
#include <memory>
#include <algorithm>

using namespace simodo;

namespace
{

    int produceLexicalAnalysis (const std::string & file_name, bool use_string_buffer, const inout::LexicalParameters & lex_param)
    {
        std::ifstream in(file_name);
        std::u16string string_buffer;
        std::unique_ptr<inout::InputStream_interface> stream;

        if (!in)
        {
            std::cout << "Ошибка при открытии файла '" << file_name << "'" << std::endl;
            return 2;
        }

        if (use_string_buffer)
        {
            inout::InputStream in_stream(in);

            while(true)
            {
                char16_t ch = in_stream.get();
                if (ch == std::char_traits<char16_t>::eof())
                    break;
                string_buffer += ch;
            }

            stream = std::make_unique<inout::RefBufferStream>(string_buffer.data());
        }
        else
            stream = std::make_unique<inout::InputStream>(in);

        inout::Tokenizer tzer(0, *stream, lex_param);

        uint32_t comment_n = 0;
        uint32_t annotation_n = 0;
        uint32_t punctuation_n = 0;
        uint32_t keyword_n = 0;
        uint32_t word_n = 0;
        uint32_t word_national_mix_error_n = 0;
        uint32_t word_national_use_error_n = 0;
        uint32_t number_n = 0;
        uint32_t number_wrong_n = 0;
        uint32_t error_n = 0;
        uint32_t unknown = 0;
        uint32_t token_count = 0;

        inout::Token t = tzer.getAnyToken();

        while (t.type() != inout::LexemeType::Empty)
        {
            token_count ++;

            switch(t.type())
            {
            case inout::LexemeType::Punctuation:
                punctuation_n ++;
                if (t.qualification() == inout::TokenQualification::Keyword)
                    keyword_n ++;
                break;
            case inout::LexemeType::Id:
                word_n ++;
                if (t.qualification() == inout::TokenQualification::NationalCharacterMix)
                    word_national_mix_error_n ++;
                break;
            case inout::LexemeType::Annotation:
                annotation_n ++;
                break;
            case inout::LexemeType::Number:
                number_n ++;
                if (t.qualification() == inout::TokenQualification::NotANumber)
                    number_wrong_n ++;
                break;
            case inout::LexemeType::Comment:
                comment_n ++;
                break;
            case inout::LexemeType::Error:
                error_n ++;
                if (t.qualification() == inout::TokenQualification::NationalCharacterMix)
                    word_national_mix_error_n ++;
                if (t.qualification() == inout::TokenQualification::NationalCharacterUse)
                    word_national_use_error_n ++;
                break;
            default:
                unknown ++;
                break;
            }

            inout::Location loc = t.makeLocation({file_name});

            std::cout << loc.uri() 
                 << ":" << loc.range().start().line() << "/" << loc.range().start().character()
                 << "-" << loc.range().end().line() << "/" << loc.range().end().character() << ", token: \""
                 << simodo::inout::toU8(t.token()) << "\"";

            if (t.token() != t.lexeme())
                std::cout << ", lexeme: \"" << inout::toU8(t.lexeme()) << "\"";

            std::cout << ", type: " << getLexemeTypeName(t.type());

            if (t.qualification() != inout::TokenQualification::None)
                std::cout << ", qualification: " << getQualificationName(t.qualification());

            std::cout << std::endl;

            t = tzer.getAnyToken();
        }

        std::cout << "ИТОГО: токенов ..... " << token_count << std::endl
             << "       комментариев. " << comment_n << std::endl
             << "       аннотаций ... " << annotation_n << std::endl
             << "       пунктуаций .. " << punctuation_n << " (из них ключевых слов: " << keyword_n << ")" << std::endl
             << "       слов ........ " << word_n  << " (из них с перемешанными алфавитами: " << word_national_mix_error_n << ")" << std::endl
             << "       чисел ....... " << number_n  << " (из них с ошибками: " << number_wrong_n << ")" << std::endl
             << "       ошибок ...... " << error_n <<
                " (из них с недопустимым алфавитом: " << word_national_use_error_n << "," <<
                " c перемешанными алфавитами: " << word_national_mix_error_n << ")" << std::endl
             << "       сбоев ....... " << unknown << std::endl
                ;

        return 0;
    }
}

int main(int argc, char *argv[])
{
    /// @todo Переделать работу с аргументами командной строки на использование 
    /// класса utility::SafeParameters

    std::vector<std::string> arguments(argv + 1, argv + argc);

    std::string	file_name       = "";
    bool    use_char16_buffer   = false;

    bool	error               = false;
    bool	help                = false;

    std::vector<inout::NumberMask> masks;

    for(size_t i=0; i < arguments.size(); ++i)
    {
        const std::string & arg = arguments[i];

        if (arg[0] == '-')
        {
            if (arg == "--help" || arg == "-h")
                help = true;
            else if (arg == "--use-char16-buffer")
                use_char16_buffer = true;
            else if (arg == "--number_mask" || arg == "-n")
            {
                if (i+2 >= arguments.size())
                    error = true;
                else {
                    masks.push_back({inout::toU16(arguments[i+1]),
                                    inout::LexemeType::Number,
                                    static_cast<inout::number_system_t>(stoi(arguments[i+2]))});
                    i += 2;
                }
            }
            else
                error = true;
        }
        else if (file_name.empty())
            file_name = arg;
        else
            error = true;
    }

    if (!help && file_name.empty())
        error = true;

    if (error)
    {
        std::cout << "Ошибка в параметрах запуска" << std::endl;
        help = true;
    }

    if (help)
        std::cout	<< "Утилита лексического анализа. Проект SIMODO." << std::endl
                << "Формат запуска:" << std::endl
                << "    <имя утилиты> [<параметры>] <файл>" << std::endl
                << "Параметры:" << std::endl
                << "    -h | --help                  - отображение подсказки по запуску программы" << std::endl
                << "         --use-char16-buffer     - провести лексический анализ заданного файла с использованием строкового буфера" << std::endl
                << "    -n | --number_mask <маска> <система счисления> - добавить маску числа" << std::endl
                ;

    if (error)
        return 1;

    if (file_name.empty())
        return 0;

    inout::LexicalParameters lex_param {
        {
            {u"/*", u"*/", u"", inout::LexemeType::Comment},
            {u"//", u"",   u"", inout::LexemeType::Comment},
            {u"\"", u"\"", u"\\", inout::LexemeType::Annotation},
            {u"'",  u"'",  u"\\", inout::LexemeType::Annotation},
        },
        {},
        u"+-,;",
        {u"_",u"_Nebuchadnezzar_II",u"ФУ_"},
        u"0123456789ABCDEF",
        u"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
        u"абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ",
        u"_",
        false,
        false,
        true
    };

    for(const inout::NumberMask & m : masks)
        lex_param.masks.push_back(m);

    return produceLexicalAnalysis(file_name, use_char16_buffer, lex_param);
}
